﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenCvSharp;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace LicenseAutoDetection
{
    class PixelInspection : IDisposable
    {
        //enum ArrayState
        //{
        //    VISITED_NONDATA = -1,
        //    NONVISITED = 0
        //}

        const int BOUND_COLOR = 240;            // The bound on image colors

        int[,] imgarr;                          // For image labeling
        GROUPINFO[,] imgarrGroup;               // For image Group
        IntPtr ptr;                             // For moving pixels
        int currentLabel = 1;                   // Group Number
        GroupList GL = new GroupList();         // For using GroupList
        GROUPHEADER LabelGroup = null;          // Every group header (큰 덩어리)

        private int StartCoordi(int _xy, int _bound)
        {
            if (_xy <= 0 && _xy < _bound - 1) return _xy;
            else if (_xy >= _bound - 1) return _bound - 1;
            else return _xy - 1;
        }

        private int xBoundCreate(int _start, int _bound)
        {
            if (_start <= 0 && _start < _bound - 1) return _start + 1;
            else if (_start >= _bound - 1) return _bound - 1;
            else return _start + 2;
        }

        private void InitGroup(int _y, int _x)
        {
            imgarr[_y, _x] = currentLabel++;
            imgarrGroup[_y, _x] = GL.AddGroup(LabelGroup, imgarr[_y, _x]);
            imgarrGroup[_y, _x].minX = imgarrGroup[_y, _x].maxX = _x;
            imgarrGroup[_y, _x].minY = imgarrGroup[_y, _x].maxY = _y;
            imgarrGroup[_y, _x].pixelCnt += 1;
            imgarrGroup[_y, _x].header.AllpixelCnt += 1;
            imgarrGroup[_y, _x].header.minX = imgarrGroup[_y, _x].header.maxX = _x;
            imgarrGroup[_y, _x].header.minY = imgarrGroup[_y, _x].header.maxY = _y;
        }

        private void AroundFind(int _y, int _x, int Hei, int Wid)
        {
            int ybound = _y, xbound, y = _y, x = _x, xplacebackup;

            y = StartCoordi(y, Hei);
            x = xplacebackup = StartCoordi(x, Wid);
            xbound = xBoundCreate(x, Wid);

            for ( ; y <= ybound; y++)
            {
                if (y == ybound) { xbound--; x = xplacebackup; }

                for ( ; x <= xbound; x++)
                {
                    // 검사 영역 내에 1이 아니거나 같은 그룹이 아닐 경우
                    if (imgarr[y, x] != -1 && imgarr[y, x] != imgarr[_y, _x])
                    {
                        if (imgarr[_y, _x] == 0)
                        {
                            imgarr[_y, _x] = imgarr[y, x];
                            imgarrGroup[_y, _x] = imgarrGroup[y, x];

                            GL.Readjust(imgarrGroup[_y, _x], _y, _x);    // 그룹 좌표 갱신

                            imgarrGroup[_y, _x].pixelCnt += 1;
                            imgarrGroup[_y, _x].header.AllpixelCnt += 1;

                            if (GL.Biggest.AllpixelCnt < imgarrGroup[_y, _x].header.AllpixelCnt)
                                GL.Biggest = imgarrGroup[_y, _x].header;
                        }
                        else
                        {
                            // 현재 저장된 픽셀과 다른 픽셀이 영역내에 있을 때
                            if (GL.IsGroup(imgarrGroup[y, x], imgarrGroup[_y, _x]) == false)  // 픽셀끼리의 헤더그룹 확인 (같은 그룹이 아닐시 진입)
                                GL.AppendGroup(imgarrGroup[_y, _x], imgarrGroup[y, x]);
                        }
                    }
                }
            }

            if (imgarr[_y, _x] == 0)   // 처음 생성된 그룹
                InitGroup(_y, _x);
        }

        public CvRect Inspect(IplImage _src)
        {
            int x, y, idxY = 0;

            imgarr = new int[_src.Height, _src.Width];
            imgarrGroup = new GROUPINFO[_src.Height, _src.Width];

            LabelGroup = new GROUPHEADER();

            ptr = _src.ImageData;

            for (y = 0; y < _src.Height; y++)
            {
                idxY = _src.WidthStep * y;
                for (x = 0; x < _src.Width; x++)
                {
                    if (imgarr[y, x] == 0)
                    {
                        if (Marshal.ReadByte(ptr, idxY + (x * 3)) <= BOUND_COLOR) AroundFind(y, x, _src.Height, _src.Width);
                        else imgarr[y, x] = -1;
                    }
                }
            }            

            return (GL.Biggest == null) ?
                new CvRect(0, 0, 0, 0) :
                new CvRect(GL.Biggest.minX, GL.Biggest.minY, (GL.Biggest.maxX - GL.Biggest.minX), (GL.Biggest.maxY - GL.Biggest.minY));
        }

        #region IDisposable 구현
        private bool disposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (imgarr != null) { Array.Clear(imgarr, 0, imgarr.Length); imgarr = null; }
                    if (imgarrGroup != null) { Array.Clear(imgarrGroup, 0, imgarrGroup.Length); imgarrGroup = null; }
                    if (LabelGroup != null) { LabelGroup = null; }
                }

                disposed = true;
            }
        }

        ~PixelInspection()
        {
            Dispose(false);
        }
        #endregion
    }
}